package com.zhudi.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhudi.algorithm.Similary;
import com.zhudi.model.enums.ErrorCode;
import com.zhudi.model.request.PageRequest;
import com.zhudi.model.request.UserLoginRequest;
import com.zhudi.model.request.UserRegisterRequest;
import com.zhudi.model.vo.UserVO;
import com.zhudi.utils.BeanChangeUtils;
import com.zhudi.expection.BusinessException;
import com.zhudi.mapper.UserMapper;
import com.zhudi.service.UserService;
import com.zhudi.model.entity.User;
import com.zhudi.utils.RedisUtil;
import com.zhudi.utils.UserUtils;
import javafx.util.Pair;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.zhudi.contant.RedisContant.PAGE_CACHE_TIME_ONE_HOUR;
import static com.zhudi.contant.RedisContant.REDIS_RECOMMEND_PAGE_KEY;
import static com.zhudi.contant.UserContant.*;

/**
* @author 朱朱朱朱蒂
* @description 针对表【user(用户信息表)】的数据库操作Service实现
* @createDate 2024-02-07 23:51:09
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService {

    @Resource
    private UserMapper userMapper;


    /**
     * @param userLoginRequest 包含登录用户名和密码的请求类
     * @param request          登录成功用于保存到session中
     * @return  用户VO
     */
    @Override
    public UserVO login(UserLoginRequest userLoginRequest, HttpServletRequest request) {
        if (userLoginRequest==null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String username = userLoginRequest.getUser_name();
        String password = userLoginRequest.getPassword();
        //账户名或者密码为空
        if (StringUtils.isAnyBlank(username,password)){
            return null;
        }

        QueryWrapper<User> userQueryWrapper=new QueryWrapper<>();
        userQueryWrapper.eq("user_name",username);
        User user = userMapper.selectOne(userQueryWrapper);
        //用户存在性检验
        if (user==null){
            return null;
        }

        //密码检验
        if (!StringUtils.equals(UserUtils.getencryptPassword(password),user.getPassword())){
            return null;

        }

        //如果都通过，说明登录成功，那么记录到session中
        request.getSession().setAttribute(USER_LOGIN_STATE,user);
        //将User信息脱敏成UserVO
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user,userVO);
        return userVO;
    }


    /**
     * @param userRegisterRequest 注册信息
     * @param request             浏览器请求
     * @return
     */
    @Override
    public Integer register(UserRegisterRequest userRegisterRequest, HttpServletRequest request) {
        if (userRegisterRequest==null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String username = userRegisterRequest.getUser_name();
        String password = userRegisterRequest.getPassword();
        String confirmPassword = userRegisterRequest.getConfirmPassword();
        //下面是参数的合法性校验
        //账户密码空格检测
        if (StringUtils.isAnyBlank(username,password,confirmPassword)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账户或密码存在空格");
        }
        //两次密码相同检测
        if (!password.equals(confirmPassword)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"两次密码不相同");
        }
        //用户名密码长度检测
        if (username.length()<USER_NAME_MIN_LENGTH){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户名长度过短"+USER_PASSWORD_MIN_LENGTH);
        }

        if (password.length()<USER_PASSWORD_MIN_LENGTH){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码长度小于"+ USER_PASSWORD_MIN_LENGTH);
        }

        //用户名重复检测
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name",username);
        if (userMapper.selectCount(queryWrapper)>0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账户名重复");
        };

        //通过检测后将账户密码放入到数据库中
        User user = new User(username,UserUtils.getencryptPassword(password));
        //todo 标签一栏插入的格式记得是:"[]"
        int id = userMapper.insert(user);
//        todo：解决id不从1开始自增的问题

        request.getSession().setAttribute(USER_LOGIN_STATE,user);;

        return id;
    }

    /**
     * 注销功能实现
     * @param request 浏览器的请求
     * @return 成功返回1
     */
    @Override
    public Integer logout(HttpServletRequest request) {
        request.getSession().removeAttribute(USER_LOGIN_STATE);

        return 1;
    }

    /**
     * 获取所有用户信息实现
     * @return 所有用户信息
     */
    @Override
    public List<UserVO> getAllUser() {
        List<User> users = userMapper.selectList(new QueryWrapper<>());
        //将User转为UserVO
        return users.stream().map(user -> {
                    UserVO userVO = new UserVO();
                    BeanUtils.copyProperties(user, userVO);
                    return userVO;
                }
        ).collect(Collectors.toList());
    }


    /**
     * 获取当前用户信息实现 todo 或许这个方法的名字可以改为getUser
     * @param userId 用户ID
     * @return 用户信息
     */
    @Override
    public User getCurrentUser(Long userId) {
        User user = userMapper.selectById(userId);
        return UserUtils.getSafetyUser(user);
    }

    /**
     * 获取登录的用户
     * @param request request
     * @return 如果获取不到的返回空
     */
    @Override
    public User getLoginUser(HttpServletRequest request){
        if(request==null){
            return null;
        }
       return (User) request.getSession().getAttribute(USER_LOGIN_STATE);
    }



    @Override
    public boolean update(User user, User loginUser) {
        if (user==null||loginUser==null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数为空");
        }
        //首先是判空检查
        long userId = user.getId();
        if (userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"目标更新用户不存在");
        }

        //接下来需要对修改的信息做合法性检查


        //用户名一般不能修改，防止绕过前端限制非法修改，后端还是要作判断
        //用户名密码长度检测
        String userName = user.getUserName();
        if(userName!=null){
            if (userName.length()<USER_NAME_MIN_LENGTH){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户名长度过短"+USER_PASSWORD_MIN_LENGTH);
            }
            //用户名重复检测
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("user_name",userName);
            //意思是除了当前这个用户还有别的用户也是这个名字，那么就不行
            queryWrapper.ne("id",userId);
            if (userMapper.selectCount(queryWrapper)>0){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"账户名重复");
            };
        }

        //密码合法性检查
        String password = user.getPassword();
        if (password!=null){
            if (password.length()<USER_PASSWORD_MIN_LENGTH){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码长度小于"+ USER_PASSWORD_MIN_LENGTH);
            }
        }
        //密码要加密
        user.setPassword(UserUtils.getencryptPassword(password));

        //性别合法性检查
        //懒得改gender为Integer，强制超出合法范围的就改为合法数值
        int gender = user.getGender();
        if (gender>1){
            user.setGender(1);
        }else if (gender<0){
            user.setGender(0);
        }

        //检查手机号合法性，包括长度以及非法字符
        if (!UserUtils.isValidPhoneNumber(user.getPhone())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }


        boolean isAdmin = loginUser.getRole()==1;
        // 如果不是管理员，只允许更新当前（自己的）信息

        if(!isAdmin&&loginUser.getId()!=userId){
            throw new BusinessException(ErrorCode.NO_AUTH,"非管理员只能更新自己的信息");
        }
        // 如果是管理员，允许更新任意用户


        return userMapper.updateById(user)==1;
    }

    /**
     * 用于检查整一个user中是否有参数为空，如果是则返回false
     * @param user 要检查的用户
     * @return 如果为空或者id小于0返回true
     */
    @Override
    public boolean isBlank(User user) {
        //todo：这里传入的user类要封装一下，因为不是检查所有的属性
        if(user==null){
            return true;
            //将id单独拆出来判断，是因为id默认值为0
        }else if(user.getId()<=0){
            return true;
        }else {

       return StringUtils.isAllBlank(user.getUserName(),user.getAvatar(),user.getNickname(),user.getPassword()
        ,user.getPhone());
        }

    }

    /**
     * @param id 要判断的用户id
     * @return 管理员为true
     */
    @Override
    public boolean isAdmin(Long id) {
        if(id<=0){
            return false;
        }
        User dbUser = userMapper.selectById(id);
        return dbUser!=null&&dbUser.getRole()==1;
    }

    /**
     * @param pageNum  页数
     * @param pageSize 每一页显示的条数
     * @return 对应页数的用户
     */
    @Override
    public Page<UserVO> getUserByPage(int pageNum, int pageSize, User loginUser) {
        //如果参数错误则抛出异常，而不是返回空，避免二义性
        if(pageSize<0||pageNum<1){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //查询数据库返回数据
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        if(loginUser==null){
            //如果为空。直接返回数据
            return BeanChangeUtils.PageUserToUserVO(userMapper.selectPage(new Page<>(pageNum,pageSize), queryWrapper));

        }else{
            //首先判断当前登录的用户是否右相关的信息缓存，如果有的话直接返回
            Long loginUserId = loginUser.getId();
            //todo:将这个修改为将id放在前面，页数和页码放在后面
            //todo 讲这段redisKey想办法抽取出来
            String redisKey = String.format(
                    REDIS_RECOMMEND_PAGE_KEY,
                    pageNum,
                    pageSize,
                    loginUserId
            );
            Page<User> redisUserPage = (Page<User>) RedisUtil.get(redisKey);
            if (redisUserPage!=null){
                return BeanChangeUtils.PageUserToUserVO(redisUserPage);
            }
            //如果没有的话，而且此时是已经登录了的，那么就返回数据，并且写入缓存
            Page<User> userPage = userMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper);
            RedisUtil.set(redisKey,userPage,PAGE_CACHE_TIME_ONE_HOUR);
            return BeanChangeUtils.PageUserToUserVO(userPage);

        }

    }

    @Override
    public Page<UserVO> matchUsersByTags(User loginUser, PageRequest pageRequest) {
        // 对于为空的pageRequest，设置默认的分页参数
        if (pageRequest == null) {
            pageRequest = new PageRequest(1, 10);
        }
        // 获取当前登录的用户标签数据，如果用户为空，抛出异常
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String loginUserTags = loginUser.getTags();

        // 如果用户标签为空，直接返回随机用户数据
        if (loginUserTags == null) {
            return getUserByPage(1, 20, loginUser);
        }

        //先去缓存中看看有没有这个id对应的缓存数据，
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        String redisKey = String.format(
                REDIS_RECOMMEND_PAGE_KEY,
                pageNum,
                pageSize,
                loginUser.getId()
        );

        Object redisCache = RedisUtil.get(redisKey);
        if (redisCache!=null){
            return (Page<UserVO>) redisCache;
        }
        // 对用户标签进行解析，获取标签频率
        String[] tags1 = Similary.parseTagString(loginUserTags);
        Map<String, Integer> loginUserTagsMap = Similary.getTagFrequency(tags1);

        // 定义查询用户的分页参数
        Page<User> page = new Page<>(pageNum, pageSize);

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 定义选择的列为：Id 和 Tags；并且过滤不符合条件的记录
        queryWrapper.select(User::getId, User::getTags).ne(User::getTags, "").ne(User::getTags, "[]");
        //全表搜索，
        List<User> userList = userMapper.selectList(queryWrapper);

        // 保存用户和相似度的记录
        List<Pair<User, Double>> recordList = new ArrayList<>();
        for (User user : userList) {
            String userTags = user.getTags();
            // 计算相似度
            Double similarityValue = Similary.calculateSimilary(loginUserTagsMap, userTags);
            recordList.add(new Pair<>(user, similarityValue));
        }

        // 根据相似度排序
        List<Pair<User, Double>> topUserPairList = recordList.stream().sorted(Comparator.comparingDouble(Pair::getValue)).collect(Collectors.toList());

        // 获取排序后的用户ID列表
        List<Long> idList = topUserPairList.stream().map(userPair->userPair.getKey().getId()).collect(Collectors.toList());

        //todo 截取出3个页面的用户要的数据

        // 重新定义查询用户的参数
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(User::getId, idList);

        // 根据用户ID列表查询用户数据
        List<User> selectList = userMapper.selectList(queryWrapper);

        // 将查询的用户转为Map数据，便于后续处理
        Map<Long, User> userMap = selectList.stream().collect(Collectors.toMap(User::getId, Function.identity()));

        // 根据用户ID列表的顺序生成UserVO列表
        List<UserVO> sortedUsersVO = idList.stream().map(id -> {
            User user = userMap.get(id);
            UserVO userVO = new UserVO();
            // 将User的属性复制到UserVO
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList());

        // 生成并返回Page对象
        Page<UserVO> userVoPage = new Page<>();
        userVoPage.setRecords(sortedUsersVO);
        userVoPage.setCurrent(page.getCurrent());
        userVoPage.setSize(page.getSize());
        //todo 这里的total应该是实际获取的数据数量
        userVoPage.setTotal(page.getTotal());

        RedisUtil.set(redisKey,userVoPage);

        return userVoPage;
    }


    /**
     * 设置某个对象的字段值
     *
     * @param target    需要设置的对象
     * @param fieldName 需要设置的字段名
     * @param value     需要设置的值
     */
    public void setFieldValue(Object target, String fieldName, Object value) {
        try {
            // 获取对象的类类型，然后获取需要的字段
            Field field = target.getClass().getDeclaredField(fieldName);

            // 这步是必须的，特别是字段是private的
            field.setAccessible(true);

            // 把值设置进去
            field.set(target, value);
        } catch (Exception e) {
            // 抛出异常
            throw new RuntimeException("Failed to set field value", e);
        }
    }





}




